57 — 调试 R3F 应用

Three.js Journey · Bruno Simon · 难度:简单

57 — 调试 R3F 应用 课程海报

简介

我们一直在使用 React 和 React Three Fiber,但还没有真正讨论过监控和调试。

这涉及 React 和 React Three Fiber 两者,而且让我们的开发生活尽可能轻松很重要,这样我们就能专注于应用本身,而不是专注于调试,也不是去弄明白为什么帧率会突然下降。

我们会先讨论 React 调试,然后聚焦于 React Three Fiber 的调试和监控。

准备工作

这个初始项目和之前的初始项目非常相似,包含一个球体、一个立方体和一个绿色的地面。

@react-three/drei 依赖已经安装在项目中,我们使用 OrbitControls 辅助工具来移动相机。

StrictMode

有一个重要的 React 特性,为了简化,我们之前一直没有使用,但你应该在所有 React 项目中都添加它,那就是 StrictMode。

StrictMode 会就你的应用中潜在的问题向你发出警告。

以下是一些例子:

把它添加到你的应用中,只需在它外面包一个标签即可。

在 /src/index.jsx 中,从 react 导入 StrictMode:

import { StrictMode } from 'react'

用一个 <StrictMode> 包裹 <Canvas>:

root.render(
    <StrictMode>
        <Canvas
            camera={ {
                fov: 45,
                near: 0.1,
                far: 50,
                position: [ - 4, 3, 6 ]
            } }
        >
            <Experience />
        </Canvas>
    </StrictMode>
)

就这样。

潜在的错误现在会在终端和控制台中显示为警告。

在接下来的课程中,我们不应该遇到特定的错误,这就是我们不打算使用它的原因,但当你开始自己探索时,应该保留 StrictMode。

不用担心 StrictMode 在生产环境中的影响。应用构建完成后它就会被忽略。

浏览器扩展

我们可以安装一个名为 React Developer Tools 的扩展,它能帮助我们实时检查和修改 component。

Chrome 和 Firefox 都可以使用

把它添加到你的浏览器中。

关闭并重新打开你的 React 应用对应的标签页,然后在开发者工具中查看 ⚛️ Components 标签(我们不打算讨论 ⚛️ Profile 标签)。

在这里,你会找到一个 component 树,并且能够实时调整它们的 props 和 state。

注意,R3F 元素(<mesh> 或 <meshBasicMaterial>)不会出现在这个树中。

不过,如果我们创建自己的包含 R3F 的 component,就可以从这里调试它们的 props 和 state。

使用 Leva 的调试 UI

到目前为止,我们还没有使用过任何调试 UI,尽管用它来调整那些属性(比如 <Text> 或 <MeshReflectorMaterial> 的)会很有用。

我们可以使用经典的 lil-gui 或任何其他替代品,比如 TweakPane、control-panel、Guifiy 等等。

但有一个由 React 实现的新选择,名为 Leva。

在我编写和录制这些课程时,Leva 仍处于 PMNDRS 团队的“密集”开发中,可能会有所变化,但和之前的课程一样,我们会强制指定版本以避免任何意外。

安装

在终端中,使用 npm install leva@0.9.34 安装 leva(我们强制指定版本以防止意外,你可以忽略潜在的安全漏洞警告)。

把它添加到我们的项目中,只需添加一个 hook 即可。

在 Experience.jsx 中,从 leva 导入 useControls:

import { useControls } from 'leva'

我们是在 Experience 中这样做的,但也可以在任何其他 component 中做,甚至可以同时在多个 component 中做。

第一个 tweak

首先,我们要创建一个 tweak,让我们可以改变球体的位置。

在 Experience 中,按如下方式调用 useControls:

export default function Experience()
{
    const controls = useControls({ position: - 2 })
    console.log(controls.position)

    // ...
}

如你所见,调试 UI 自动出现在右上角。

我们可以移动它、缩小它,甚至可以搜索特定的 tweak。

如果你直接在 tweak 中改变值,你会看到控制台用新的值再次打印日志。这意味着 component 正在被重新渲染。

虽然这是应该避免的,但它并不一定是坏事,而在这里它是合理的,因为确实有一个值发生了变化。

另外,React 和 R3F 知道什么发生了变化,只会更新严格必要的内容。

在使用 position 值之前,我们可以对它进行解构,以便只获取 position:

export default function Experience()
{
    const { position } = useControls({ position: - 2 })

    console.log(position)

    // ...
}

现在我们可以把它用在橙色球体 <mesh> 的 position-x 属性上:

<mesh position-x={ position }>
lesson demo video preview

如果你改变 Tweak 值,你会看到球体的位置发生变化。

范围

不错,但不太方便。与其必须写一个值,我们更希望有一个范围。

这可以通过向 useControls 的 position 传递一个带有特定属性的对象来实现:

const { position } = useControls({
    position:
    {
        value: - 2,
        min: - 4,
        max: 4,
        step: 0.01
    }
})
lesson demo video preview

现在我们可以拖动滑块来改变值。

向量

好多了。但如果我们想在 y 轴上做同样的事呢?

与其添加第二个范围 tweak,我们将使用一个 vector 2 tweak:

const { position } = useControls({
    position:
    {
        value: { x: - 2, y: 0 },
        step: 0.01
    }
})

别忘了更新 <mesh> component:

<mesh position={ [ position.x, position.y, 0 ] }>
lesson demo video preview

现在我们可以用某种摇杆来移动它。

但我们能更进一步吗?

在 useControls 中添加一个 z 值:

const { position } = useControls({
    position:
    {
        value: { x: - 2, y: 0, z: 0 },
        step: 0.01
    }
})

并相应地更新 <mesh>:

<mesh position={ [ position.x, position.y, position.z ] }>
lesson demo video preview

如你所见,是的,它能用,但我们失去了摇杆,这很合理,因为 3D 摇杆不太方便。

太可惜了,让我们回到一个 vector 2 tweak:

const { position } = useControls({
    position:
    {
        value: { x: - 2, y: 0 },
        step: 0.01
    }
})

然后更新 <mesh>:

<mesh position={ [ position.x, position.y, 0 ] }>

你可能已经注意到 y 轴似乎是反的。

我们可以通过向 tweak 添加 joystick: 'invertY' 来解决这个问题:

const { position } = useControls({
    position:
    {
        value: { x: - 2, y: 0 },
        step: 0.01,
        joystick: 'invertY'
    }
})
lesson demo video preview

摇杆的范围是有限的,但把光标贴到边缘可以让值超出初始范围。

颜色

我们可以通过传递一个看起来像颜色的字符串来使用颜色。

在 useControls 中添加一个 color 属性:

const { position } = useControls({
    // ...
    color: '#ff0000'
})

添加 tweak 时,别忘了在解构中把它取为一个变量:

const { position, color } = useControls({
    // ...
})

并把它添加到球体的 <meshStandardMaterial>:

<meshStandardMaterial color={ color } />
lesson demo video preview

我们可以使用各种颜色格式:

注意,这样表达颜色时 alpha 通道不会生效,因为 Three.js 通过 opacity 和 transparent 属性来处理 alpha。

Leva 会保留输入格式,而 Three.js 也以同样的方式支持它们。

布尔值

向 useControls 添加一个 visible 属性并解构它:

const { position, color, visible } = useControls({
    // ...
    visible: true
})

在球体 <mesh> 上把它用于其 visible 属性:

<mesh visible={ visible } position={ [ position.x, position.y, 0 ] }>
lesson demo video preview

现在我们可以切换球体的可见性。

区间

虽然我们不会在场景中使用它,但我们可以设置一个区间,它类似于范围,但有两个光标(不需要解构它):

const { position, color, visible } = useControls({
    // ...
    myInterval:
    {
        min: 0,
        max: 10,
        value: [4, 5],
    }
})
lesson demo video preview

按钮

同样,我们不会在场景中使用它,但我们可以创建一个按钮,点击后会调用一个函数。

为此,我们需要从 leva 导入 button:

import { button, useControls } from 'leva'

把它作为该属性的值调用,并把要调用的函数作为参数传给它:

const { position, color, visible } = useControls('sphere', {
	  // ...
	  clickMe: button(() => { console.log('ok') })
})
lesson demo video preview

选择

我们可以通过在 options 属性上设置一个数组来创建一个选择输入:

const { position, color, visible } = useControls('sphere', {
    // ...
    choice: { options: [ 'a', 'b', 'c' ] }
})
lesson demo video preview

文件夹

和 lil-gui 类似,如果太多 tweak 让事情变得有点太复杂,我们可以把它们组织到文件夹中。

为此,把字符串作为 useControls 的第一个参数传入:

const { position, color, visible } = useControls('sphere', {
    position:
    {
        value: { x: - 2, y: 0 },
        step: 0.01,
        joystick: 'invertY'
    },
    color: 'orange',
    visible: true
})

要创建另一个文件夹,再次调用 useControls,但使用不同的名称,并在其中放入你想要的任何内容,比如一个 scale(虽然我们没有把它和 cube 关联起来,但你可以自己试着做一下):

const { scale } = useControls('cube', {
    scale:
    {
        value: 1.5,
        step: 0.01,
        min: 0,
        max: 5
    }
})

如果你需要在文件夹内再创建一个文件夹,可以使用可以从 leva 导入的 folder 函数来实现,但我们这里不会涉及。

配置

最后,我们可以配置 Leva。

首先,我们需要自己把这个 component 添加到应用中,以便可以调整它的属性。

我们不能把它添加到 Experience 中,因为它位于 <Canvas> 内,而 <Canvas> 内的一切都是为 R3F 准备的。相反,我们需要把它添加到 index.jsx 文件中 <Canvas> 之外。

在 index.jsx 中,从 leva 导入 Leva:

import { Leva } from 'leva'

把它添加到 <Canvas> 之外:

root.render(
    <StrictMode>
        <Leva />
        <Canvas
            camera={ {
                fov: 45,
                near: 0.1,
                far: 50,
                position: [ - 4, 3, 6 ]
            } }
        >
            <Experience />
        </Canvas>
    </StrictMode>
)

现在我们可以添加像 collapsed 这样的属性:

<Leva collapsed />

查看文档以了解更多属性 https://github.com/pmndrs/leva/blob/main/docs/configuration.md

如果你想要更多示例 https://codesandbox.io/examples/package/leva

使用 r3f-perf 进行监控

说到监控,我们仍然可以使用传统的解决方案,比如 Stats.js,但 Renaud Rohlinger(@onirenaud)开发了一个专门为 R3F 打造的更先进的解决方案,名为 R3F-Perf。

在你的终端中,使用 npm install r3f-perf@7.2 添加 r3f-perf 依赖(我们强制指定版本以防止意外,你可以忽略潜在的安全漏洞警告)。

在 Experience.jsx 中,从 r3f-perf 导入 Perf:

import { Perf } from 'r3f-perf'

并把它添加到你的 JSX 中:

export default function Experience()
{
    // ...

    return <>

        <Perf />

        {/* ... */}

    </>
}

默认情况下,界面位于右上角,会与 Leva 冲突,但我们可以用 position 属性来改变它:

<Perf position="top-left" />

R3F-Perf 会显示数量惊人的有用信息。

我们甚至能访问绘制调用次数、内存使用量、渲染场景所花的时间等等。

为了让事情更有趣一点,我们可以给 Leva 添加一个 perfVisible 复选框:

const { perfVisible } = useControls({
    perfVisible: true
})

并让 <Perf> 具有条件性:

{ perfVisible && <Perf position="top-left" /> }
lesson demo video preview

这样,我们就可以从我们的调试 UI 中切换它

查看文档以了解更多关于 R3F-Perf 的信息 https://github.com/utsuboco/r3f-perf